package com.redis.service;


import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.redis.Filter.BloomFilterHelper;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;

import javax.annotation.Resource;
import java.io.Serializable;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Service
public class RedisServer {



    private static final Logger LOGGER = LoggerFactory.getLogger(RedisServer.class);


    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    private final RedisTemplate<String, Serializable> limitRedisTemplate;

    @Autowired
    public RedisServer(RedisTemplate<String, Serializable> limitRedisTemplate) {
        this.limitRedisTemplate = limitRedisTemplate;
    }

    private static final Long SUCCESS = 1L;
    /**
     * 写入缓存
     * @param key
     * @param value
     * @return
     */
    public boolean set(final String key, Object value) {
        boolean result = false;
        try {
            ValueOperations<String, Object> operations = redisTemplate.opsForValue();
            operations.set(key, value);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }
    /**
     * 写入缓存设置时效时间
     * @param key
     * @param value
     * @return
     */
    public boolean set(final String key, Object value, Long expireTime,TimeUnit unit) {
        boolean result = false;
        try {
            ValueOperations<String, Object> operations = redisTemplate.opsForValue();
            operations.set(key, value);
            redisTemplate.expire(key, expireTime, unit);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }
    /**
     * 批量删除对应的value
     * @param keys
     */
    public void remove(final String... keys) {
        for (String key : keys) {
            remove(key);
        }
    }

    /**
     * 批量删除key
     * @param pattern
     */
    public void removePattern(final String pattern) {
        Set<String> keys = redisTemplate.keys(pattern);
        if (keys.size() > 0) {
            redisTemplate.delete(keys);
        }
    }
    /**
     * 删除对应的value
     * @param key
     */
    public void remove(final String key) {
        if (exists(key)) {
            redisTemplate.delete(key);
        }
    }
    /**
     * 判断缓存中是否有对应的value
     * @param key
     * @return
     */
    public boolean exists(final String key) {
        return redisTemplate.hasKey(key);
    }
    /**
     * 读取缓存
     * @param key
     * @return
     */
    public Object get(final String key) {
        Object result = null;
        ValueOperations<String, Object> operations = redisTemplate.opsForValue();
        result = operations.get(key);
        return result;
    }

    /**
     * 获取指定key的值
     */
    public <T> T get1(String key) {
        return (T) redisTemplate.opsForValue().get(key);
    }
    /**
     * 哈希 添加
     * @param key
     * @param hashKey
     * @param value
     */
    public void hmSet(String key, Object hashKey, Object value){
        HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
        hash.put(key,hashKey,value);
    }

    /**
     * 哈希获取数据
     * @param key
     * @param hashKey
     * @return
     */
    public Object hmGet(String key, Object hashKey){
        HashOperations<String, Object, Object>  hash = redisTemplate.opsForHash();
        return hash.get(key,hashKey);
    }

    /**
     * 列表添加
     * @param k
     * @param v
     */
    public void lPush(String k,Object v){
        ListOperations<String, Object> list = redisTemplate.opsForList();
        list.rightPush(k,v);
    }

    /**
     * 列表获取
     * @param k
     * @param l
     * @param l1
     * @return
     */
    public List<Object> lRange(String k, long l, long l1){
        ListOperations<String, Object> list = redisTemplate.opsForList();
        return list.range(k,l,l1);
    }

    /**
     * 集合添加
     * @param key
     * @param value
     */
    public void add(String key,Object value){
        SetOperations<String, Object> set = redisTemplate.opsForSet();
        set.add(key,value);
    }

    /**
     * 集合获取
     * @param key
     * @return
     */
    public Set<Object> setMembers(String key){
        SetOperations<String, Object> set = redisTemplate.opsForSet();
        return set.members(key);
    }

    /**
     * 有序集合添加
     * @param key
     * @param value
     * @param scoure
     */
    public void zAdd(String key,Object value,double scoure){
        ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
        zset.add(key,value,scoure);
    }

    /**
     * 有序集合获取
     * @param key
     * @param scoure
     * @param scoure1
     * @return
     */
    public Set<Object> rangeByScore(String key,double scoure,double scoure1){
        ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
        return zset.rangeByScore(key, scoure, scoure1);
    }

    /**
     * redis 实现限流功能  Jedis
     * @param userId
     * @param actionKey
     * @param period
     * @param maxCount
     * @return
     */
//    public boolean isActionAllowed(String userId, String actionKey, int period,
//                                   int maxCount) throws Exception {
//        Jedis jedis = new Jedis("192.168.234.3", 6379);
//        // 使用用户编号 + 行为作为 KEY 。这样，我们就可以统计某个用户的操作行为。
//        String key = String.format("hist:%s:%s", userId, actionKey);
//        long nowTs = System.currentTimeMillis(); // 获取当前时间。
//        Pipeline pipe = jedis.pipelined(); // pipeline 批量操作，提升效率。
//        pipe.multi(); // 此处启动了事务，可以保证指令的原子性。
//        pipe.zadd(key, nowTs, "" + nowTs); // zset 添加，key value score 要看下。
//        // zremrangeByScore ，移除 当前时间 一个小时之前的数据
//        pipe.zremrangeByScore(key, 0, nowTs - (period * 1000));
//
//        Response<Long> count = (Response<Long>) pipe.zcard(key); // zcard ，计算 zset 的数量
//        pipe.expire(key, period + 1); // 设置过期。这里多 + 1 秒，为了防止网络延迟。
//        pipe.exec(); // pipeline 执行
//        pipe.close();
//
//        return count.get() <= maxCount; // 是否超过最大次数。
//    }

    /**
     * redisTempleate+lua脚本实现分布式限流
     * @param key
     * @param time  限制时间单位秒
     * @param limitCount  限制次数
     * @return
     */
    public boolean getRedisLimit(String key,int time,int limitCount ){
        boolean flag;

        ImmutableList<String> keys = ImmutableList.of(key);
        String luaScript =
                "local c = redis.call('get',KEYS[1])\n" +
                // 调用不超过最大值，则直接返回
                "if c and tonumber(c) > tonumber(ARGV[1]) then\n" +
                "return c;\n" +
                "end\n" +
                //执行计算器自加
                "c = redis.call('incr',KEYS[1])\n" +
                //从第一次调用开始限流，设置对应键值的过期
                "if tonumber(c) == 1 then\n" +
                "redis.call('expire',KEYS[1],ARGV[2])\n" +
                "end\n" +
                "return c;";
            RedisScript<Number> redisScript = new DefaultRedisScript<>(luaScript, Number.class);
            // 通过execute 执行 lua脚本
            Number count = limitRedisTemplate.execute(redisScript, keys, limitCount, time);
            if (count != null && count.intValue() <= limitCount) {
                flag = true;
            } else {
                flag = false;
            }
        return flag;
    }

    /**
     * redis实现分布式锁 setnx+lua脚本实现 只适用于单机部署
     */
    public boolean getLock(String key, String value, int expireTime) {
        String script = "if redis.call('setNX',KEYS[1],ARGV[1]) == 1 " +
                        "then if redis.call('get',KEYS[1])==ARGV[1] " +
                        "then return redis.call('expire',KEYS[1],ARGV[2])" +
                        " else return 0 end end";

        RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
        Long result = redisTemplate.execute(redisScript, Lists.newArrayList(key), value, expireTime);
        return SUCCESS.equals(result);
    }

    /**
     * 解锁
     * 只适用于单机部署
     */
    public boolean releaseLock(String key, String value) {
        //获取锁key 并且value 匹配释放锁
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] " +
                        "then return redis.call('del', KEYS[1])" +
                        "else return 0 end";
        RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
        Long result = redisTemplate.execute(redisScript, Lists.newArrayList(key), value);

        return SUCCESS.equals(result);
    }


    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";

    /**
     * 尝试获取分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @param expireTime 超期时间
     * @return 是否获取成功
     */
//    public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
//        /**
//         * 第一个key
//         * 第二个value
//         * nx : 当key 存在是不处理，key不存在 进行set 操作
//         * 第四个参数 给key 设置过去时间和第五个参数对应
//         * 第五个： 设置过期时间
//         */
//        String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
//
//        if (LOCK_SUCCESS.equals(result)) {
//            return true;
//        }
//        return false;
//
//    }

    /**
     * 释放分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @return 是否释放成功
     */
    private static final Long RELEASE_SUCCESS = 1L;

    public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
        //首先获取锁对应的value值，检查是否与requestId相等，如果相等则删除锁（解锁）
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] " +
                              "then return redis.call('del', KEYS[1])" +
                         " else return 0 end";
        Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));

        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        return false;

    }

    /**
     * redis实现分布式锁 redlock实现
     */
    public boolean lock(String lockName, long expireSeconds) {
        //todo 配置文件中读取
        Config config = new Config();
        config.useSentinelServers().addSentinelAddress("127.0.0.1:6369","127.0.0.1:6379", "127.0.0.1:6389")
                .setMasterName("masterName")
                .setPassword("password").setDatabase(0);
        RedissonClient redissonClient = Redisson.create(config);
        // 还可以getFairLock(), getReadWriteLock()
        RLock redLock = redissonClient.getLock(lockName);

        boolean isLock= false;
        try {
            // 500ms拿不到锁, 就认为获取锁失败。10000ms即10s是锁失效时间。
            isLock = redLock.tryLock(500, expireSeconds, TimeUnit.MILLISECONDS);
            if (isLock) {
                LOGGER.info("获取Redisson分布式锁[成功],lockName={}", lockName);
            } else {
                LOGGER.info("获取Redisson分布式锁[失败],lockName={}", lockName);
            }
        } catch (Exception e) {
            LOGGER.error("获取Redisson分布式锁[异常]，lockName=" + lockName, e);
            e.printStackTrace();
            return false;
        } finally {
            //无论如何，最后都需要解锁
            redLock.unlock();
        }
        return isLock;
    }

    /**
     * 解锁
     * @param lockName
     */
    public void release(String lockName) {

        //todo 配置文件中读取
        Config config = new Config();
        config.useSentinelServers().addSentinelAddress("127.0.0.1:6369","127.0.0.1:6379", "127.0.0.1:6389")
                .setMasterName("masterName")
                .setPassword("password").setDatabase(0);
        RedissonClient redissonClient = Redisson.create(config);
        //解锁
        redissonClient.getLock(lockName).unlock();
    }

    /**
     * 自增值，不设置过期时间
     * @param key
     * @param delta  增量值
     * @return
     */
    public long increment(String key, long delta) {
        return redisTemplate.opsForValue().increment(key, delta);
    }

    /**
     * 自增值，设置过期时间
     */
    public long increment(String key, long delta, long timeout, TimeUnit unit) {
        long result = redisTemplate.opsForValue().increment(key, delta);

        //首次自增
        if (result == delta) {
            redisTemplate.expire(key, timeout, unit);
        }

        return result;
    }

    /**
     * 同时设置多个值，不设置过期时间
     */
    public void multiSet(Map<String, Object> map) {
        redisTemplate.opsForValue().multiSet(map);
    }

    /**
     * 同时设置多个值，设置过期时间
     */
    public void multiSet(Map<String, Object> map, long timeout, TimeUnit unit) {
        redisTemplate.opsForValue().multiSet(map);
        for (String key : map.keySet()) {
            redisTemplate.expire(key, timeout, unit);
        }
    }

    /**
     * 列表左边push
     */
    public void leftPush(String key, Object value) {
        redisTemplate.opsForList().leftPush(key, value);
    }

    /**
     * 列表左边批量push
     */
    public void leftPushAll(String key, Collection<Object> values) {
        redisTemplate.opsForList().leftPushAll(key, values);
    }

    /**
     * 根据给定的布隆过滤器添加值
     */
    public <T> void addByBloomFilter(BloomFilterHelper<T> bloomFilterHelper, String key, T value) {
        Preconditions.checkArgument(bloomFilterHelper != null, "bloomFilterHelper不能为空");
        int[] offset = bloomFilterHelper.murmurHashOffset(value);
        for (int i : offset) {
            redisTemplate.opsForValue().setBit(key, i, true);
        }
    }

    /**
     * 根据给定的布隆过滤器判断值是否存在
     */
    public <T> boolean includeByBloomFilter(BloomFilterHelper<T> bloomFilterHelper, String key, T value) {
        Preconditions.checkArgument(bloomFilterHelper != null, "bloomFilterHelper不能为空");
        int[] offset = bloomFilterHelper.murmurHashOffset(value);
        for (int i : offset) {
            if (!redisTemplate.opsForValue().getBit(key, i)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 通过zset 数据结构实现滑动窗口
     * @param intervalTime
     */
    public void limitFlow(long intervalTime){
        Long currentTime = new Date().getTime();
        System.out.println(currentTime);
        if(redisTemplate.hasKey("limit")) {
            Integer count = redisTemplate.opsForZSet().rangeByScore("limit", currentTime -  intervalTime, currentTime).size();        // intervalTime是限流的时间
            System.out.println(count);
            if (count != null && count > 5) {
                System.out.println("每分钟最多只能访问5次");
            }
        }
        redisTemplate.opsForZSet().add("limit",UUID.randomUUID().toString(),currentTime);
        System.out.println("访问成功");
    }



}
